package com.katze.boot.applet.ssh.controller;

import com.katze.boot.applet.ssh.service.SecureShellService;
import com.katze.boot.plugins.security.annotations.RestControllerMapping;
import com.katze.boot.plugins.security.annotations.RestRequestMapping;
import com.katze.common.Message;
import com.katze.common.controller.BaseController;
import com.katze.ssh.SSHException;
import com.katze.ssh.SshSessionKey;
import com.katze.ssh.SshSessionPool;
import com.katze.ssh.sftp.FileEntry;
import com.katze.ssh.sftp.SftpChannel;
import com.katze.ssh.sftp.SftpDownloadTask;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@RestControllerMapping("/sftp")
public class SftpController extends BaseController {
    private static final Logger log = LoggerFactory.getLogger(SftpController.class);

    @Autowired
    private SecureShellService secureShellService;

    @Autowired
    private SshSessionPool sessionPool;

    @Resource(name = "sftpSseEmitterPoolExecutor")
    private ThreadPoolExecutor executor;

    @RestRequestMapping(value = "/home/{id}", description = "获取工作用户根目录", permits = "secure:ssh:view:sftp")
    public Message home(@PathVariable("id") String id) throws Exception {
        SshSessionKey key = secureShellService.getSessionProperties(id);
        try (SftpChannel channel = sessionPool.borrow(key, SftpChannel.class)) {
            return Message.ok().data(channel.home()).add("host", key.getHost()).add("path", channel.getHomePath());
        }
    }

    /**
     * 获取目录下所有文件
     * @param id    会话ID
     * @param path  路径，为空时代表当前用户目录
     */
    @RestRequestMapping(value = "/ls/{id}", skip = true)
    public Message ls(@PathVariable("id") String id, String path, Boolean isDir) throws Exception {
        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            List<FileEntry> list = new ArrayList<>();
            String finalPath = StringUtils.isBlank(path) ? channel.getHomePath() : path;
            channel.ls(finalPath, (dir, stream) -> stream.map(item -> new FileEntry(dir, item))
                    .filter(item -> isDir == null || isDir == item.isDirectory())
                    .sorted((a, b) -> Long.compare(b.getAttributes().getModifyTime(), a.getAttributes().getModifyTime()))
                    .forEach(list::add));
            return this.toDataTable(list).add("path", finalPath);
        } catch (SSHException e) {
            return Message.error(e.getMessage());
        }
    }

    /**
     * 新建文件夹
     * @param id    会话ID
     * @param path  路径
     * @param filename  文件名（默认：新建文件夹）
     */
    @RestRequestMapping(value = "/mkdir/{id}", method = RequestMethod.POST, description = "新建文件夹")
    public Message mkdir(@PathVariable("id") String id,  String path, String filename) throws Exception {
        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            if (filename == null || filename.isBlank()) {
                return Message.ok().data(Map.of("fullPath", channel.mkdir(path)));
            } else {
                return Message.ok().data(Map.of("fullPath", channel.mkdirs(path, filename)));
            }
        } catch (SSHException e) {
            return Message.error(e.getMessage());
        }
    }

    /**
     * 重命名
     * @param id    会话ID
     * @param path  路径
     * @param oldFilename   旧文件名
     * @param newFilename   新文件名
     */
    @RestRequestMapping(value = "/rename/{id}", method = RequestMethod.PUT, description = "重命名")
    public Message rename(@PathVariable("id") String id, String path, String oldFilename, String newFilename) throws Exception {
        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            boolean isDir = channel.rename(path, oldFilename, newFilename).isDirectory();
            Map<String, String> data = new HashMap<>();
            FileEntry.resolveFilename(newFilename, isDir).accept((name, extension) -> {
                data.put("filename", newFilename);
                data.put("name", name);
                data.put("extension", extension);
            });
            return Message.ok("重命名成功").data(data);
        } catch (SSHException e) {
            return Message.error(e.getMessage());
        }
    }

    /**
     * 删除文件（支持递归删除）
     * @param id    会话ID
     * @param path  路径
     * @param filenames 文件名
     */
    @RestRequestMapping(value = "/rmf/{id}", method = RequestMethod.DELETE, description = "删除文件")
    public Message rmf(@PathVariable("id") String id, String path, String[] filenames) throws Exception {
        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            Message message = Message.ok();
            channel.rmf(path, filenames).accept((d, f) -> {
                message.add("dir", d);
                message.add("doc", f);
            });
            Integer c1 = (Integer)message.get("dir");
            Integer c2 = (Integer)message.get("doc");
            return Message.ok("删除成功,共计:" + (c1 > 0? c1 + "个目录":"") + (c1 > 0 && c2 > 0? ",":"") + (c2 > 0? c2 + "个文件":""));
        } catch (SSHException e) {
            return Message.error(e.getMessage());
        }
    }

    /**
     * 移动文件
     * @param id    会话ID
     * @param source    起始目录
     * @param target    目标目录
     * @param filenames 文件名
     */
    @RestRequestMapping(value = "/mv/{id}", method = RequestMethod.PUT, description = "移动文件")
    public Message mv(@PathVariable("id") String id, String source, String target, String[] filenames) throws Exception{
        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            List<FileEntry> list = channel.mv(source, target, filenames);
            return Message.ok("移动成功").data(list);
        } catch (SSHException e) {
            return Message.error(e.getMessage());
        }
    }

    /**
     * 复制文件
     * @param id    会话ID
     * @param source    起始目录
     * @param target    目标目录
     * @param filenames 文件名
     */
    @RestRequestMapping(value = "/cp/{id}", method = RequestMethod.PUT, description = "复制文件")
    public Message cp(@PathVariable("id") String id, String source, String target, String[] filenames) throws Exception{
        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            List<FileEntry> list = channel.cp(source, target, filenames);
            return Message.ok("复制成功").data(list);
        } catch (SSHException e) {
            return Message.error(e.getMessage());
        }
    }

    @RestRequestMapping(value = "/download/task/{id}", method = RequestMethod.POST, description = "下载文件")
    public void download(HttpServletResponse response, @PathVariable("id") String id, String path, String[] filenames) throws Exception {
        String name = filenames.length == 1? filenames[0]:path.substring(path.lastIndexOf("/") + 1);
        int position = name.lastIndexOf(".");
        if (position > 0) {
            name = name.substring(0, position);
        }

        try (SftpChannel channel = sessionPool.borrow(secureShellService.getSessionProperties(id), SftpChannel.class)){
            response.setContentType("application/octet-stream");
            response.setHeader("Access-Control-Expose-Headers", "Content-Filename");
            response.setHeader("Content-Filename", URLEncoder.encode(name.concat(".zip"), StandardCharsets.UTF_8));
            Map<String, List<String>> data = new LinkedHashMap<>();
            data.put(path, Arrays.asList(filenames));
            channel.download(id, response.getOutputStream(), data);
            response.flushBuffer();
        } catch (AsyncRequestNotUsableException e) {
            log.warn("用户取消下载操作:{}---{}", path, filenames);
        }
    }

    @RestRequestMapping(value = "/progress", skip = true, description = "下载进度", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter progress(String id) {
        SseEmitter emitter = new SseEmitter(0L);
        emitter.onCompletion(() -> log.info("线程池当前下载进度任务数:{}", executor.getQueue().size()));
        executor.submit(() -> {
            try {
                SftpDownloadTask task = SftpChannel.PROGRESS.get(id);
                if (task == null) {
                    emitter.send("startup");
                } else {
                    emitter.send("continue");
                }

                int count = 0;
                do {
                    task = SftpChannel.PROGRESS.get(id);
                    count++;
                    if (count % 10 == 0) {
                        emitter.send("请稍后,正在等待下载请求...");
                    }
                    Thread.sleep(100);
                } while (task == null && count <= 50);

                 if (task != null){
                     while (task.isDoing()) {
                         if (task.isLoading()) {
                             emitter.send("正在加载文件...");
                             TimeUnit.MILLISECONDS.sleep(100);
                         } else {
                             emitter.send(task.toString());
                             TimeUnit.MILLISECONDS.sleep(200);
                         }
                     }
                }
                emitter.send("shutdown");
            } catch (Throwable e) {
                log.error("{}:发送失败", id, e);
                Thread.currentThread().interrupt();
            } finally {
                emitter.complete();
            }
        });
        return emitter;
    }
}
